import { isNil } from 'lodash-es'
import { Data, TreeNode } from '../index';


export const sleep = (ms: number): Promise<number> => new Promise(resolve => setTimeout(resolve, ms))

export const asyncDownloadFile = (blob: Blob, filename: string): void => {
  const url = window.URL.createObjectURL(blob)
  const link: HTMLAnchorElement = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.setAttribute('download', filename)
  if (typeof link.download === 'undefined') {
    link.setAttribute('target', '_blank')
  }
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  window.URL.revokeObjectURL(url)
}

export const removeNullValues = (values: any): void => {
  values &&
  Object.keys(values).forEach(key => {
    if (values[key] === null || values[key] === undefined) {
      delete values[key]
    }
  })
}

export const putChild = (key: any, childKey: any, childValue: any): void => {
  if (!isNil(childValue) && !isNil(key)) {
    key[childKey] = childValue
  }
}

export function listToTree(treeNodes: TreeNode[]): TreeNode[] {
  const map = new Map<number, TreeNode>(treeNodes.map(node => [node.id, node]))
  const virtualRoot = {} as Partial<TreeNode>
  treeNodes.forEach(node => {
    const parent = map.get(node.pid) ?? virtualRoot
    ;(parent.children ??= []).push(node)
  })
  return virtualRoot.children ?? []
}

export const clearObject = (obj: Data): Data => {
  for (const key in obj) {
    delete obj[key as string]
  }
  return obj
}

export function uuid() {
  //@ts-ignore
  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16),
  ).replace(/-/g, '');
}

interface INode<K, V> {
  key: K
  value: V
  prev: INode<K, V> | null
  next: INode<K, V> | null
}

export class Lru<K, V> {
  private readonly capacity: number
  private map: Map<K, INode<K, V>>
  private readonly head: INode<K, V>
  private readonly tail: INode<K, V>

  constructor(capacity: number) {
    this.capacity = capacity
    this.map = new Map()
    this.head = {} as INode<K, V>
    this.tail = {} as INode<K, V>
    this.head.next = this.tail
    this.tail.prev = this.head
  }

  get(key: K): V | null {
    const node = this.map.get(key)
    if (!node) return null

    // 移除节点并移动到头部
    this.removeNode(node)
    this.addToHead(node)

    return node.value
  }

  put(key: K, value: V): void {
    if (this.map.has(key)) {
      const node = this.map.get(key)!
      node.value = value
      this.removeNode(node)
      this.addToHead(node)
    } else {
      const newNode: INode<K, V> = {
        key,
        value,
        prev: null,
        next: null,
      }

      if (this.map.size >= this.capacity) {
        const tailNode = this.removeTail()
        this.map.delete(tailNode.key)
      }

      this.map.set(key, newNode)
      this.addToHead(newNode)
    }
  }

  private addToHead(node: INode<K, V>): void {
    node.prev = this.head
    node.next = this.head.next

    if (this.head.next) {
      this.head.next.prev = node
    }
    this.head.next = node
  }

  private removeNode(node: INode<K, V>): void {
    const prevNode = node.prev!
    const nextNode = node.next!

    prevNode.next = nextNode
    nextNode.prev = prevNode
  }

  private removeTail(): INode<K, V> {
    const tailNode = this.tail.prev!
    this.removeNode(tailNode)
    return tailNode
  }
}
